// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include <queue>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/power_monitor/power_monitor.h"
#include "base/run_loop.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/power_monitor_test_base.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/test_suite.h"
#include "media/base/cdm_context.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/cast/common/rtp_time.h"
#include "media/cast/constants.h"
#include "media/cast/sender/h264_vt_encoder.h"
#include "media/cast/sender/video_frame_factory.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/video_utility.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

const int kVideoWidth = 1280;
const int kVideoHeight = 720;

class MediaTestSuite : public base::TestSuite {
public:
    MediaTestSuite(int argc, char** argv)
        : TestSuite(argc, argv)
    {
    }
    ~MediaTestSuite() final { }

protected:
    void Initialize() final;
};

void MediaTestSuite::Initialize()
{
    base::TestSuite::Initialize();
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    command_line->AppendSwitch(switches::kEnableInbandTextTracks);
    media::InitializeMediaLibrary();
}

} // namespace

namespace media {
namespace cast {

    // See comment in end2end_unittest.cc for details on this value.
    const double kVideoAcceptedPSNR = 38.0;

    void SaveDecoderInitResult(bool* out_result, bool in_result)
    {
        *out_result = in_result;
    }

    void SaveOperationalStatus(OperationalStatus* out_status,
        OperationalStatus in_status)
    {
        *out_status = in_status;
    }

    class MetadataRecorder : public base::RefCountedThreadSafe<MetadataRecorder> {
    public:
        MetadataRecorder()
            : count_frames_delivered_(0)
        {
        }

        int count_frames_delivered() const { return count_frames_delivered_; }

        void PushExpectation(FrameId expected_frame_id,
            FrameId expected_last_referenced_frame_id,
            RtpTimeTicks expected_rtp_timestamp,
            const base::TimeTicks& expected_reference_time)
        {
            expectations_.push(Expectation { expected_frame_id,
                expected_last_referenced_frame_id,
                expected_rtp_timestamp,
                expected_reference_time });
        }

        void CompareFrameWithExpected(
            std::unique_ptr<SenderEncodedFrame> encoded_frame)
        {
            ASSERT_LT(0u, expectations_.size());
            auto e = expectations_.front();
            expectations_.pop();
            if (e.expected_frame_id != e.expected_last_referenced_frame_id) {
                EXPECT_EQ(EncodedFrame::DEPENDENT, encoded_frame->dependency);
            } else {
                EXPECT_EQ(EncodedFrame::KEY, encoded_frame->dependency);
            }
            EXPECT_EQ(e.expected_frame_id, encoded_frame->frame_id);
            EXPECT_EQ(e.expected_last_referenced_frame_id,
                encoded_frame->referenced_frame_id)
                << "frame id: " << e.expected_frame_id;
            EXPECT_EQ(e.expected_rtp_timestamp, encoded_frame->rtp_timestamp);
            EXPECT_EQ(e.expected_reference_time, encoded_frame->reference_time);
            EXPECT_FALSE(encoded_frame->data.empty());
            ++count_frames_delivered_;
        }

    private:
        friend class base::RefCountedThreadSafe<MetadataRecorder>;
        virtual ~MetadataRecorder() { }

        int count_frames_delivered_;

        struct Expectation {
            FrameId expected_frame_id;
            FrameId expected_last_referenced_frame_id;
            RtpTimeTicks expected_rtp_timestamp;
            base::TimeTicks expected_reference_time;
        };
        std::queue<Expectation> expectations_;

        DISALLOW_COPY_AND_ASSIGN(MetadataRecorder);
    };

    class EndToEndFrameChecker
        : public base::RefCountedThreadSafe<EndToEndFrameChecker> {
    public:
        explicit EndToEndFrameChecker(const VideoDecoderConfig& config)
            : decoder_()
            , count_frames_checked_(0)
        {
            bool decoder_init_result;
            decoder_.Initialize(
                config, false, nullptr,
                base::Bind(&SaveDecoderInitResult, &decoder_init_result),
                base::Bind(&EndToEndFrameChecker::CompareFrameWithExpected,
                    base::Unretained(this)));
            base::RunLoop().RunUntilIdle();
            EXPECT_TRUE(decoder_init_result);
        }

        void PushExpectation(const scoped_refptr<VideoFrame>& frame)
        {
            expectations_.push(frame);
        }

        void EncodeDone(std::unique_ptr<SenderEncodedFrame> encoded_frame)
        {
            auto buffer = DecoderBuffer::CopyFrom(encoded_frame->bytes(),
                encoded_frame->data.size());
            decoder_.Decode(buffer, base::Bind(&EndToEndFrameChecker::DecodeDone, base::Unretained(this)));
        }

        void CompareFrameWithExpected(const scoped_refptr<VideoFrame>& frame)
        {
            ASSERT_LT(0u, expectations_.size());
            auto& e = expectations_.front();
            expectations_.pop();
            EXPECT_LE(kVideoAcceptedPSNR, I420PSNR(e, frame));
            ++count_frames_checked_;
        }

        void DecodeDone(DecodeStatus status) { EXPECT_EQ(DecodeStatus::OK, status); }

        int count_frames_checked() const { return count_frames_checked_; }

    private:
        friend class base::RefCountedThreadSafe<EndToEndFrameChecker>;
        virtual ~EndToEndFrameChecker() { }

        FFmpegVideoDecoder decoder_;
        std::queue<scoped_refptr<VideoFrame>> expectations_;
        int count_frames_checked_;

        DISALLOW_COPY_AND_ASSIGN(EndToEndFrameChecker);
    };

    void CreateFrameAndMemsetPlane(VideoFrameFactory* const video_frame_factory)
    {
        const scoped_refptr<media::VideoFrame> video_frame = video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta());
        ASSERT_TRUE(video_frame.get());
        auto* cv_pixel_buffer = video_frame->cv_pixel_buffer();
        ASSERT_TRUE(cv_pixel_buffer);
        CVPixelBufferLockBaseAddress(cv_pixel_buffer, 0);
        auto* ptr = CVPixelBufferGetBaseAddressOfPlane(cv_pixel_buffer, 0);
        ASSERT_TRUE(ptr);
        memset(ptr, 0xfe, CVPixelBufferGetBytesPerRowOfPlane(cv_pixel_buffer, 0) * CVPixelBufferGetHeightOfPlane(cv_pixel_buffer, 0));
        CVPixelBufferUnlockBaseAddress(cv_pixel_buffer, 0);
    }

    void NoopFrameEncodedCallback(
        std::unique_ptr<media::cast::SenderEncodedFrame> /*encoded_frame*/) { }

    class TestPowerSource : public base::PowerMonitorSource {
    public:
        void GenerateSuspendEvent()
        {
            ProcessPowerEvent(SUSPEND_EVENT);
            base::RunLoop().RunUntilIdle();
        }
        void GenerateResumeEvent()
        {
            ProcessPowerEvent(RESUME_EVENT);
            base::RunLoop().RunUntilIdle();
        }

    private:
        bool IsOnBatteryPowerImpl() final { return false; }
    };

    class H264VideoToolboxEncoderTest : public ::testing::Test {
    protected:
        H264VideoToolboxEncoderTest() = default;

        void SetUp() final
        {
            clock_ = new base::SimpleTestTickClock();
            clock_->Advance(base::TimeTicks::Now() - base::TimeTicks());

            power_source_ = new TestPowerSource();
            power_monitor_.reset(new base::PowerMonitor(
                std::unique_ptr<TestPowerSource>(power_source_)));

            cast_environment_ = new CastEnvironment(
                std::unique_ptr<base::TickClock>(clock_), message_loop_.task_runner(),
                message_loop_.task_runner(), message_loop_.task_runner());
            encoder_.reset(new H264VideoToolboxEncoder(
                cast_environment_, video_sender_config_,
                base::Bind(&SaveOperationalStatus, &operational_status_)));
            base::RunLoop().RunUntilIdle();
            EXPECT_EQ(STATUS_INITIALIZED, operational_status_);
        }

        void TearDown() final
        {
            encoder_.reset();
            base::RunLoop().RunUntilIdle();
            power_monitor_.reset();
        }

        void AdvanceClockAndVideoFrameTimestamp()
        {
            clock_->Advance(base::TimeDelta::FromMilliseconds(33));
            frame_->set_timestamp(frame_->timestamp() + base::TimeDelta::FromMilliseconds(33));
        }

        static void SetUpTestCase()
        {
            // Reusable test data.
            video_sender_config_ = GetDefaultVideoSenderConfig();
            video_sender_config_.codec = CODEC_VIDEO_H264;
            const gfx::Size size(kVideoWidth, kVideoHeight);
            frame_ = media::VideoFrame::CreateFrame(
                PIXEL_FORMAT_I420, size, gfx::Rect(size), size, base::TimeDelta());
            PopulateVideoFrame(frame_.get(), 123);
        }

        static void TearDownTestCase() { frame_ = nullptr; }

        static scoped_refptr<media::VideoFrame> frame_;
        static FrameSenderConfig video_sender_config_;

        base::SimpleTestTickClock* clock_; // Owned by CastEnvironment.
        base::MessageLoop message_loop_;
        scoped_refptr<CastEnvironment> cast_environment_;
        std::unique_ptr<VideoEncoder> encoder_;
        OperationalStatus operational_status_;
        TestPowerSource* power_source_; // Owned by the power monitor.
        std::unique_ptr<base::PowerMonitor> power_monitor_;

    private:
        DISALLOW_COPY_AND_ASSIGN(H264VideoToolboxEncoderTest);
    };

    // static
    scoped_refptr<media::VideoFrame> H264VideoToolboxEncoderTest::frame_;
    FrameSenderConfig H264VideoToolboxEncoderTest::video_sender_config_;

    // Failed on mac_chromium_rel_ng trybot. http://crbug.com/627260
    TEST_F(H264VideoToolboxEncoderTest, DISABLED_CheckFrameMetadataSequence)
    {
        scoped_refptr<MetadataRecorder> metadata_recorder(new MetadataRecorder());
        VideoEncoder::FrameEncodedCallback cb = base::Bind(
            &MetadataRecorder::CompareFrameWithExpected, metadata_recorder.get());

        metadata_recorder->PushExpectation(
            FrameId::first(), FrameId::first(),
            RtpTimeTicks::FromTimeDelta(frame_->timestamp(), kVideoFrequency),
            clock_->NowTicks());
        EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
        base::RunLoop().RunUntilIdle();

        for (FrameId frame_id = FrameId::first() + 1;
             frame_id < FrameId::first() + 10; ++frame_id) {
            AdvanceClockAndVideoFrameTimestamp();
            metadata_recorder->PushExpectation(
                frame_id, frame_id - 1,
                RtpTimeTicks::FromTimeDelta(frame_->timestamp(), kVideoFrequency),
                clock_->NowTicks());
            EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
        }

        encoder_.reset();
        base::RunLoop().RunUntilIdle();

        EXPECT_EQ(10, metadata_recorder->count_frames_delivered());
    }

#if defined(USE_PROPRIETARY_CODECS)
    // Failed on mac_chromium_rel_ng trybot. http://crbug.com/627260
    TEST_F(H264VideoToolboxEncoderTest, DISABLED_CheckFramesAreDecodable)
    {
        VideoDecoderConfig config(kCodecH264, H264PROFILE_MAIN, frame_->format(),
            COLOR_SPACE_UNSPECIFIED, frame_->coded_size(),
            frame_->visible_rect(), frame_->natural_size(),
            EmptyExtraData(), Unencrypted());
        scoped_refptr<EndToEndFrameChecker> checker(new EndToEndFrameChecker(config));

        VideoEncoder::FrameEncodedCallback cb = base::Bind(&EndToEndFrameChecker::EncodeDone, checker.get());
        for (FrameId frame_id = FrameId::first(); frame_id < FrameId::first() + 6;
             ++frame_id) {
            checker->PushExpectation(frame_);
            EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
            AdvanceClockAndVideoFrameTimestamp();
        }

        encoder_.reset();
        base::RunLoop().RunUntilIdle();

        EXPECT_EQ(5, checker->count_frames_checked());
    }
#endif

    TEST_F(H264VideoToolboxEncoderTest, CheckVideoFrameFactory)
    {
        auto video_frame_factory = encoder_->CreateVideoFrameFactory();
        ASSERT_TRUE(video_frame_factory.get());
        // The first call to |MaybeCreateFrame| will return null but post a task to
        // the encoder to initialize for the specified frame size. We then drain the
        // message loop. After that, the encoder should have initialized and we
        // request a frame again.
        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
        base::RunLoop().RunUntilIdle();
        CreateFrameAndMemsetPlane(video_frame_factory.get());
    }

    TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoring)
    {
        // Encode a frame, suspend, encode a frame, resume, encode a frame.

        VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
        EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
        power_source_->GenerateSuspendEvent();
        EXPECT_FALSE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
        power_source_->GenerateResumeEvent();
        EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
    }

    TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoringNoInitialFrame)
    {
        // Suspend, encode a frame, resume, encode a frame.

        VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
        power_source_->GenerateSuspendEvent();
        EXPECT_FALSE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
        power_source_->GenerateResumeEvent();
        EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
    }

    TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoringVideoFrameFactory)
    {
        VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
        auto video_frame_factory = encoder_->CreateVideoFrameFactory();
        ASSERT_TRUE(video_frame_factory.get());

        // The first call to |MaybeCreateFrame| will return null but post a task to
        // the encoder to initialize for the specified frame size. We then drain the
        // message loop. After that, the encoder should have initialized and we
        // request a frame again.
        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
        base::RunLoop().RunUntilIdle();
        CreateFrameAndMemsetPlane(video_frame_factory.get());

        // After a power suspension, the factory should not produce frames.
        power_source_->GenerateSuspendEvent();

        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
        base::RunLoop().RunUntilIdle();
        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));

        // After a power resume event, the factory should produce frames right away
        // because the encoder re-initializes on its own.
        power_source_->GenerateResumeEvent();
        CreateFrameAndMemsetPlane(video_frame_factory.get());
    }

    TEST_F(H264VideoToolboxEncoderTest,
        CheckPowerMonitoringVideoFrameFactoryNoInitialFrame)
    {
        VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
        auto video_frame_factory = encoder_->CreateVideoFrameFactory();
        ASSERT_TRUE(video_frame_factory.get());

        // After a power suspension, the factory should not produce frames.
        power_source_->GenerateSuspendEvent();

        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
        base::RunLoop().RunUntilIdle();
        ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
            gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));

        // After a power resume event, the factory should produce frames right away
        // because the encoder re-initializes on its own.
        power_source_->GenerateResumeEvent();
        CreateFrameAndMemsetPlane(video_frame_factory.get());
    }

} // namespace cast
} // namespace media
